package com.xinqi.common.third.onekey.client;

import com.xinqi.common.base.error.CommonErrors;
import com.xinqi.common.base.utils.json.JSON;
import com.xinqi.common.base.utils.uuids.UUIDs;
import com.xinqi.common.third.onekey.request.OneKeyCheckReq;
import com.xinqi.common.third.onekey.response.OneKeyCheckRsp;
import feign.Feign;
import feign.Headers;
import feign.Logger;
import feign.RequestLine;
import feign.RequestTemplate;
import feign.codec.EncodeException;
import feign.codec.Encoder;
import feign.form.UrlencodedFormContentProcessor;
import feign.jackson.JacksonDecoder;
import feign.slf4j.Slf4jLogger;
import org.apache.commons.codec.digest.DigestUtils;

import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * 网易易盾一键登录
 * {@url https://support.dun.163.com/documents/287305921855672320?docId=288803729972580352}
 *
 * @author linkai
 * @since 2017/5/17
 */
@Headers({
    "Content-Type: application/x-www-form-urlencoded"
})
public interface OneKeyHelp {

    /**
     * 校验登录
     */
    @RequestLine("POST /oneclick/check")
    OneKeyCheckRsp check(OneKeyCheckReq dto);

    static OneKeyHelp create(String secretId, String secretKey, String businessId, String url) {
        Feign.Builder builder = Feign.builder();
        builder
            .encoder(new Holder.OneKeyEncoder(secretId, secretKey, businessId))
            .decoder(new JacksonDecoder(JSON.mapper()))
            .client(new feign.okhttp.OkHttpClient())
            .logger(new Slf4jLogger())
            .logLevel(Logger.Level.FULL);
        return builder.target(OneKeyHelp.class, url);
    }


    final class Holder {
        private static final class OneKeyEncoder implements Encoder {
            private String secretId;
            private String secretKey;
            private String businessId;
            private UrlencodedFormContentProcessor formContentProcessor;

            public OneKeyEncoder(String secretId, String secretKey, String businessId) {
                this.secretId = secretId;
                this.secretKey = secretKey;
                this.businessId = businessId;
            }

            @Override
            public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
                if (object instanceof OneKeyCheckReq) {
                    OneKeyCheckReq data = (OneKeyCheckReq) object;
                    Map<String, Object> params = new HashMap<>();
                    params.put("secretId", secretId);
                    params.put("businessId", businessId);
                    params.put("version", "v1");
                    // 格式为时间戳格式, 与当前时间差值不能超过6
                    params.put("timestamp", String.valueOf(System.currentTimeMillis()));
                    // 32随机字符串
                    params.put("nonce", UUIDs.uuidStr()); // 32随机字符串
                    // 2.设置私有参数d
                    params.put("accessToken", data.getAccessToken()); // 运营商预取号获取到的token
                    params.put("token", data.getToken()); // 易盾返回的token
                    // 3.生成签名信息
                    String signature = genSignature(secretKey, params);
                    params.put("signature", signature);
                    formContentProcessor.process(template, StandardCharsets.UTF_8, params);
                } else {
                    throw CommonErrors.BAD_REQUEST.asException("一键登录请求类错误");
                }
            }

            private String genSignature(String secretKey, Map<String, Object> params) {
                // 1. 参数名按照ASCII码表升序排序
                String[] keys = params.keySet().toArray(new String[0]);
                Arrays.sort(keys);
                // 2. 按照排序拼接参数名与参数值
                StringBuilder sb = new StringBuilder();
                for (String key : keys) {
                    sb.append(key).append(params.get(key) == null ? "" : params.get(key));
                }
                // 3. 将secretKey拼接到最后
                sb.append(secretKey);
                // 4. MD5是128位长度的摘要算法，用16进制表示，一个十六进制的字符能表示4个位，所以签名后的字符串长度固定为32个十六进制字符。
                return DigestUtils.md5Hex(sb.toString().getBytes(StandardCharsets.UTF_8));
            }
        }
    }
}
